﻿using System;
using System.Collections.Generic;

using System.Text;
using RoughJs.Error;
using RoughJs.Helper;

namespace RoughJs.Ast
{
    /// <summary>
    /// 本地对象实现的基方法
    /// </summary>
    public abstract class IdScriptableObject : ScriptableObject, IdFunctionCall
    {
        private volatile PrototypeValues prototypeValues;
        public class PrototypeValues
        {
            private static int VALUE_SLOT = 0;
            private static int NAME_SLOT = 1;
            private static int SLOT_SPAN = 2;

            private IdScriptableObject obj;
            private int maxId;
            private volatile Object[] valueArray;
            private volatile short[] attributeArray;
            private volatile int lastFoundId = 1;

            // The following helps to avoid creation of valueArray during runtime
            // initialization for common case of "constructor" property
            public int constructorId;
            private IdFunctionObject constructor;
            private short constructorAttrs;

            public PrototypeValues(IdScriptableObject obj, int maxId)
            {
                if (obj == null) throw new ArgumentException();
                if (maxId < 1) throw new ArgumentException();
                this.obj = obj;
                this.maxId = maxId;
            }

            public int getMaxId()
            {
                return maxId;
            }

            /// <summary>
            /// 初始化原型
            /// </summary>
            /// <param name="id"></param>
            /// <param name="name"></param>
            /// <param name="value"></param>
            /// <param name="attributes"></param>
            public void initValue(int id, String name, Object value, int attributes)
            {
                if (!(1 <= id && id <= maxId))
                    throw new ArgumentException();
                if (name == null)
                    throw new ArgumentException();
                if (value == NOT_FOUND)
                    throw new ArgumentException();
                ScriptableObject.checkValidAttributes(attributes);
                if (obj.findPrototypeId(name) != id)
                    throw new ArgumentException(name);

                if (id == constructorId)
                {
                    if (!(value is IdFunctionObject))
                    {
                        throw new ArgumentException("consructor should be initialized with IdFunctionObject");
                    }
                    constructor = (IdFunctionObject)value;
                    constructorAttrs = (short)attributes;
                    return;
                }

                initSlot(id, name, value, attributes);
            }



            private void initSlot(int id, String name, Object value,
                                  int attributes)
            {
                Object[] array = valueArray;
                if (array == null)
                    throw new Exception();

                if (value == null)
                {
                    value = UniqueTag.NULL_VALUE;
                }
                int index = (id - 1) * SLOT_SPAN;
                lock (this)
                {
                    Object value2 = array[index + VALUE_SLOT];
                    if (value2 == null)
                    {
                        array[index + VALUE_SLOT] = value;
                        array[index + NAME_SLOT] = name;
                        attributeArray[id - 1] = (short)attributes;
                    }
                    else
                    {
                        if (!(name == array[index + NAME_SLOT]))
                            throw new Exception();
                    }
                }
            }

            public IdFunctionObject createPrecachedConstructor()
            {
                if (constructorId != 0) throw new Exception();
                constructorId = obj.findPrototypeId("constructor");//Scriptable得到prototype的id
                if (constructorId == 0)
                {
                    throw new Exception(
                        "No id for constructor property");
                }
                obj.initPrototypeId(constructorId);
                if (constructor == null)
                {
                    throw new Exception(
                        obj.GetType().Name + ".initPrototypeId() did not "
                        + "initialize id=" + constructorId);
                }
                constructor.initFunction(obj.getClassName(),
                                         ScriptableObject.getTopLevelScope(obj));
                constructor.markAsConstructor(obj);
                return constructor;
            }

            public int findId(String name)
            {
                Object[] array = valueArray;
                if (array == null)
                {
                    return obj.findPrototypeId(name);
                }
                int id = lastFoundId;
                if (name == array[(id - 1) * SLOT_SPAN + NAME_SLOT])
                {
                    return id;
                }
                id = obj.findPrototypeId(name);
                if (id != 0)
                {
                    int nameSlot = (id - 1) * SLOT_SPAN + NAME_SLOT;
                    // Make cache to work!
                    array[nameSlot] = name;
                    lastFoundId = id;
                }
                return id;
            }

            public bool has(int id)
            {
                Object[] array = valueArray;
                if (array == null)
                {
                    // Not yet initialized, assume all exists
                    return true;
                }
                int valueSlot = (id - 1) * SLOT_SPAN + VALUE_SLOT;
                Object value = array[valueSlot];
                if (value == null)
                {
                    // The particular entry has not been yet initialized
                    return true;
                }
                return value != NOT_FOUND;
            }

            public Object get(int id)
            {
                Object value = ensureId(id);
                if (value == UniqueTag.NULL_VALUE)
                {
                    value = null;
                }
                return value;
            }

            public void set(int id, Scriptable start, Object value)
            {
                if (value == NOT_FOUND) throw new ArgumentException();
                ensureId(id);
                int attr = attributeArray[id - 1];
                if ((attr & READONLY) == 0)
                {
                    if (start == obj)
                    {
                        if (value == null)
                        {
                            value = UniqueTag.NULL_VALUE;
                        }
                        int valueSlot = (id - 1) * SLOT_SPAN + VALUE_SLOT;
                        lock (this)
                        {
                            valueArray[valueSlot] = value;
                        }
                    }
                    else
                    {
                        int nameSlot = (id - 1) * SLOT_SPAN + NAME_SLOT;
                        String name = (String)valueArray[nameSlot];
                        start.put(name, start, value);
                    }
                }
            }

            public void delete(int id)
            {
                ensureId(id);
                int attr = attributeArray[id - 1];
                if ((attr & PERMANENT) == 0)
                {
                    int valueSlot = (id - 1) * SLOT_SPAN + VALUE_SLOT;
                    lock (this)
                    {
                        valueArray[valueSlot] = NOT_FOUND;
                        attributeArray[id - 1] = EMPTY;
                    }
                }
            }

            public int getAttributes(int id)
            {
                ensureId(id);
                return attributeArray[id - 1];
            }

            public void setAttributes(int id, int attributes)
            {
                ScriptableObject.checkValidAttributes(attributes);
                ensureId(id);
                lock (this)
                {
                    attributeArray[id - 1] = (short)attributes;
                }
            }

            public Object[] getNames(bool getAll, Object[] extraEntries)
            {
                Object[] names = null;
                int count = 0;
                for (int id = 1; id <= maxId; ++id)
                {
                    Object value = ensureId(id);
                    if (getAll || (attributeArray[id - 1] & DONTENUM) == 0)
                    {
                        if (value != NOT_FOUND)
                        {
                            int nameSlot = (id - 1) * SLOT_SPAN + NAME_SLOT;
                            String name = (String)valueArray[nameSlot];
                            if (names == null)
                            {
                                names = new Object[maxId];
                            }
                            names[count++] = name;
                        }
                    }
                }
                if (count == 0)
                {
                    return extraEntries;
                }
                else if (extraEntries == null || extraEntries.Length == 0)
                {
                    if (count != names.Length)
                    {
                        Object[] tmp = new Object[count];
                        Array.Copy(names, 0, tmp, 0, count);
                        names = tmp;
                    }
                    return names;
                }
                else
                {
                    int extra = extraEntries.Length;
                    Object[] tmp = new Object[extra + count];
                    Array.Copy(extraEntries, 0, tmp, 0, extra);
                    Array.Copy(names, 0, tmp, extra, count);
                    return tmp;
                }
            }

            private Object ensureId(int id)
            {
                Object[] array = valueArray;
                if (array == null)
                {
                    lock (this)
                    {
                        array = valueArray;
                        if (array == null)
                        {
                            array = new Object[maxId * SLOT_SPAN];
                            valueArray = array;
                            attributeArray = new short[maxId];
                        }
                    }
                }
                int valueSlot = (id - 1) * SLOT_SPAN + VALUE_SLOT;
                Object value = array[valueSlot];
                if (value == null)
                {
                    if (id == constructorId)
                    {
                        initSlot(constructorId, "constructor",
                                 constructor, constructorAttrs);
                        constructor = null; // no need to refer it any longer
                    }
                    else
                    {
                        obj.initPrototypeId(id);
                    }
                    value = array[valueSlot];
                    if (value == null)
                    {
                        throw new Exception(
                            obj.GetType().Name + ".initPrototypeId(int id) "
                            + "did not initialize id=" + id);
                    }
                }
                return value;
            }
        }

        public IdScriptableObject()
        {
        }

        public IdScriptableObject(Scriptable scope, Scriptable prototype)
            : base(scope, prototype)
        {

        }

        #region IdFunctionCall Members

        public virtual object execIdCall(IdFunctionObject f, Context cx, Scriptable scope, Scriptable thisObj, object[] args)
        {
            throw new NotImplementedException();
        }

        #endregion

        public virtual void initPrototypeId(int id)
        {
            throw new Exception(id.ToString());
        }

        public virtual int findPrototypeId(String name)
        {
            throw new Exception(name);
        }
        /// <summary>
        /// 导入作为Js类
        /// </summary>
        /// <param name="maxPrototypeId">最大prototypeid</param>
        /// <param name="scope">作用域</param>
        /// <param name="sealed1">是否封装</param>
        /// <returns></returns>
        public IdFunctionObject exportAsJSClass(int maxPrototypeId,
                                                 Scriptable scope,
                                                 bool sealed1)
        {
            // Set scope and prototype unless this is top level scope itself
            if (scope != this && scope != null)
            {
                setParentScope(scope);
                setPrototype(getObjectPrototype(scope));//设置作用域的prototype
            }

            activatePrototypeMap(maxPrototypeId);
            IdFunctionObject ctor = prototypeValues.createPrecachedConstructor();//构造function
            if (sealed1)
            {
                sealObject();
            }
            fillConstructorProperties(ctor);
            if (sealed1)
            {
                ctor.sealObject();
            }
            ctor.exportAsScopeProperty();//加入父作用域作为属性
            return ctor;
        }
        public virtual void fillConstructorProperties(IdFunctionObject ctor)
        {
        }
        //protected void initPrototypeId(int id)
        //{
        //    throw new Exception(id.ToString());
        //}

        //protected int findPrototypeId(String name)
        //{
        //    throw new Exception(name);
        //}

        public void activatePrototypeMap(int maxPrototypeId)
        {
            PrototypeValues values = new PrototypeValues(this, maxPrototypeId);
            lock (this)
            {
                if (prototypeValues != null)
                    throw new Exception();
                prototypeValues = values;
            }
        }
        /// <summary>
        /// 初始化prototype
        /// </summary>
        /// <param name="tag">标志，类型，例如Function</param>
        /// <param name="id">方法id号</param>
        /// <param name="name">方法名</param>
        /// <param name="arity"></param>
        public void initPrototypeMethod(Object tag, int id, String name,
                                          int arity)
        {
            Scriptable scope = ScriptableObject.getTopLevelScope(this);
            IdFunctionObject f = newIdFunction(tag, id, name, arity, scope);
            prototypeValues.initValue(id, name, f, DONTENUM);
        }
        /// <summary>
        /// 创建一个idFunctionObject
        /// </summary>
        /// <param name="tag"></param>
        /// <param name="id"></param>
        /// <param name="name"></param>
        /// <param name="arity"></param>
        /// <param name="scope"></param>
        /// <returns></returns>
        private IdFunctionObject newIdFunction(Object tag, int id, String name,
                                           int arity, Scriptable scope)
        {
            IdFunctionObject f = new IdFunctionObject(this, tag, id, name, arity,
                                                      scope);
            if (isSealed()) { f.sealObject(); }
            return f;
        }

        public static EcmaError incompatibleCallError(IdFunctionObject f)
        {
            throw ScriptRuntime.typeError1("msg.incompat.call",
                                           f.getFunctionName());
        }

        /**
     * Map name to id of instance property.
     * Should return 0 if not found or the result of
     * {@link #instanceIdInfo(int, int)}.
     */
        public virtual int findInstanceIdInfo(String name)
        {
            return 0;
        }

        /** Map id back to property name it defines.
         */
        public virtual String getInstanceIdName(int id)
        {
            throw new ArgumentException(id.ToString());
        }

        /** Get id value.
         ** If id value is constant, descendant can call cacheIdValue to store
         ** value in the permanent cache.
         ** Default implementation creates IdFunctionObject instance for given id
         ** and cache its value
         */
        public virtual Object getInstanceIdValue(int id)
        {
            throw new Exception(id.ToString());
        }

        /**
         * Set or delete id value. If value == NOT_FOUND , the implementation
         * should make sure that the following getInstanceIdValue return NOT_FOUND.
         */
        public virtual void setInstanceIdValue(int id, Object value)
        {
            throw new Exception(id.ToString());
        }
        public static int instanceIdInfo(int attributes, int id)
        {
            return (attributes << 16) | id;
        }

        public override Object[] getIds(bool getAll)
        {
            Object[] result = base.getIds(getAll);

            if (prototypeValues != null)
            {
                result = prototypeValues.getNames(getAll, result);
            }

            int maxInstanceId = getMaxInstanceId();
            if (maxInstanceId != 0)
            {
                Object[] ids = null;
                int count = 0;

                for (int id = maxInstanceId; id != 0; --id)
                {
                    String name = getInstanceIdName(id);
                    int info = findInstanceIdInfo(name);
                    if (info != 0)
                    {
                        int attr = MathKit.foo(info, 16);
                        if ((attr & PERMANENT) == 0)
                        {
                            if (NOT_FOUND == getInstanceIdValue(id))
                            {
                                continue;
                            }
                        }
                        if (getAll || (attr & DONTENUM) == 0)
                        {
                            if (count == 0)
                            {
                                // Need extra room for no more then [1..id] names
                                ids = new Object[id];
                            }
                            ids[count++] = name;
                        }
                    }
                }
                if (count != 0)
                {
                    if (result.Length == 0 && ids.Length == count)
                    {
                        result = ids;
                    }
                    else
                    {
                        Object[] tmp = new Object[result.Length + count];
                        Array.Copy(result, 0, tmp, 0, result.Length);
                        Array.Copy(ids, 0, tmp, result.Length, count);
                        result = tmp;
                    }
                }
            }
            return result;
        }

        /// <summary>
        /// 得到最大的id号
        /// </summary>
        /// <returns></returns>
        public virtual int getMaxInstanceId()
        {
            return 0;
        }

        public void addIdFunctionProperty(Scriptable obj, Object tag, int id,
                                                 String name, int arity)
        {
            Scriptable scope = ScriptableObject.getTopLevelScope(obj);
            IdFunctionObject f = newIdFunction(tag, id, name, arity, scope);
            f.addAsProperty(obj);
        }
        public Object defaultGet(String name)
        {
            return base.get(name, this);
        }

        public void defaultPut(String name, Object value)
        {
            base.put(name, this, value);
        }

        public override Object get(String name, Scriptable start)
        {
            int info = findInstanceIdInfo(name);
            if (info != 0)
            {
                int id = (info & 0xFFFF);
                return getInstanceIdValue(id);
            }
            if (prototypeValues != null)
            {
                int id = prototypeValues.findId(name);
                if (id != 0)
                {
                    return prototypeValues.get(id);
                }
            }
            return base.get(name, start);
        }

        public override void put(String name, Scriptable start, Object value)
        {
            int info = findInstanceIdInfo(name);
            if (info != 0)
            {
                if (start == this && isSealed())
                {
                    throw Context.reportRuntimeError1("msg.modify.sealed",
                                                      name);
                }
                int attr = MathKit.foo(info, 16);
                if ((attr & READONLY) == 0)
                {
                    if (start == this)
                    {
                        int id = (info & 0xFFFF);
                        setInstanceIdValue(id, value);
                    }
                    else
                    {
                        start.put(name, start, value);
                    }
                }
                return;
            }
            if (prototypeValues != null)
            {
                int id = prototypeValues.findId(name);
                if (id != 0)
                {
                    if (start == this && isSealed())
                    {
                        throw Context.reportRuntimeError1("msg.modify.sealed",
                                                          name);
                    }
                    prototypeValues.set(id, start, value);
                    return;
                }
            }
            base.put(name, start, value);
        }


         public override void delete(String name)
    {
        int info = findInstanceIdInfo(name);
        if (info != 0) {
            // Let the super class to throw exceptions for sealed objects
            if (!isSealed()) {
                int attr = MathKit.foo(info , 16);
                if ((attr & PERMANENT) == 0) {
                    int id = (info & 0xFFFF);
                    setInstanceIdValue(id, NOT_FOUND);
                }
                return;
            }
        }
        if (prototypeValues != null) {
            int id = prototypeValues.findId(name);
            if (id != 0) {
                if (!isSealed()) {
                    prototypeValues.delete(id);
                }
                return;
            }
        }
        base.delete(name);
    }

        public override int getAttributes(String name)
    {
        int info = findInstanceIdInfo(name);
        if (info != 0) {
            int attr =MathKit.foo (info , 16);
            return attr;
        }
        if (prototypeValues != null) {
            int id = prototypeValues.findId(name);
            if (id != 0) {
                return prototypeValues.getAttributes(id);
            }
        }
        return base.getAttributes(name);
    }

    public override void setAttributes(String name, int attributes)
    {
        ScriptableObject.checkValidAttributes(attributes);
        int info = findInstanceIdInfo(name);
        if (info != 0) {
            int currentAttributes =MathKit.foo (info, 16);
            if (attributes != currentAttributes) {
                throw new Exception(
                    "Change of attributes for this id is not supported");
            }
            return;
        }
        if (prototypeValues != null) {
            int id = prototypeValues.findId(name);
            if (id != 0) {
                prototypeValues.setAttributes(id, attributes);
                return;
            }
        }
        base.setAttributes(name, attributes);
    }

  public  void initPrototypeConstructor(IdFunctionObject f)
    {
        int id = prototypeValues.constructorId;
        if (id == 0)
            throw new Exception();
        if (f.methodId() != id)
            throw new ArgumentException();
        if (isSealed()) { f.sealObject(); }
        prototypeValues.initValue(id, "constructor", f, DONTENUM);
    }


        //public bool isGetterOrSetter(String name, int index, bool setter)
        //{
        //    Slot slot = getSlot(name, index, SLOT_QUERY);
        //    if (slot is GetterSlot)
        //    {
        //        if (setter && ((GetterSlot)slot).setter != null) return true;
        //        if (!setter && ((GetterSlot)slot).getter != null) return true;
        //    }
        //    return false;
        //}

    }
}
